/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using DaveSexton.DocProject.Resources;
using System.IO;

namespace DaveSexton.DocProject
{
	public sealed class CommentsManager : System.ComponentModel.ISupportInitialize
	{
		#region Public Properties
		public string CommentsPath
		{
			get
			{
				return commentsPath;
			}
		}

		public string ProjectXmlFileName
		{
			get
			{
				return projectXmlFileName;
			}
		}

		public bool HasChanges
		{
			get
			{
				return hasChanges;
			}
		}

		public bool Initializing
		{
			get
			{
				return initializing;
			}
		}

		/// <remarks>
		/// To get a reference to the project's comments file, pass null or an empty string as the <paramref name="assembly"/> argument.
		/// The <see cref="ProjectXmlFileName"/> value is then automatically used as the document's file name.
		/// </remarks>
		public CommentsDocument this[string assembly]
		{
			get
			{
				if (!assemblyDocuments.ContainsKey(assembly ?? string.Empty))
				{
					CommentsDocument document = new CommentsDocument(assembly, 
						Path.Combine(commentsPath, GetAssemblyCommentsFileName(assembly)));

					if (initializing)
						document.BeginInit();

					document.DeleteSection += (s, e) => OnDeleteSection(e);

					document.Changed += (s, e) =>
						{
							if (!initializing)
								hasChanges = true;
						};

					document.FileCreated += delegate(object sender, EventArgs e)
					{
						OnFileCreated(new FileEventArgs(document.FilePath));
					};

					assemblyDocuments.Add(assembly ?? string.Empty, document);
				}

				return assemblyDocuments[assembly ?? string.Empty];
			}
		}
		#endregion

		#region Private / Protected
		private bool hasChanges, initializing;
		private readonly Dictionary<string, CommentsDocument> assemblyDocuments = new Dictionary<string, CommentsDocument>(8);
		private readonly string commentsPath, projectXmlFileName;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="CommentsManager" /> class.
		/// </summary>
		public CommentsManager(string commentsPath, string projectXmlFileName)
		{
			if (string.IsNullOrEmpty(commentsPath))
				throw new ArgumentException(Errors.NullOrEmpty, "commentsPath");

			if (string.IsNullOrEmpty(projectXmlFileName))
				throw new ArgumentException(Errors.NullOrEmpty, "projectXmlFileName");

			this.commentsPath = commentsPath;
			this.projectXmlFileName = projectXmlFileName;
		}
		#endregion

		#region Methods
		public string GetAssemblyCommentsFileName(string assembly)
		{
			if (string.IsNullOrEmpty(assembly))
				return projectXmlFileName;
			else
				return assembly + ".xml";
		}

		public void Save(IAnyProject project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			foreach (CommentsDocument document in assemblyDocuments.Values)
			{
				if (document.HasChanges)
				{
					project.GetItem(document.FilePath).Checkout();
					document.Save();
				}
			}

			hasChanges = false;
		}
		#endregion

		#region Events
		private readonly object FileCreatedEventLock = new object();
		private EventHandler<FileEventArgs> FileCreatedEvent;
		private readonly object DeleteSectionEventLock = new object();
		private EventHandler<CancelCommentsMemberSectionEventArgs> DeleteSectionEvent;

		/// <summary>
		/// Event raised when a member's section is being deleted.
		/// </summary>
		public event EventHandler<CancelCommentsMemberSectionEventArgs> DeleteSection
		{
			add
			{
				lock (DeleteSectionEventLock)
				{
					DeleteSectionEvent += value;
				}
			}
			remove
			{
				lock (DeleteSectionEventLock)
				{
					DeleteSectionEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DeleteSection" /> event.
		/// </summary>
		/// <param name="e"><see cref="CancelCommentsMemberSectionEventArgs" /> object that provides the arguments for the event.</param>
		private void OnDeleteSection(CancelCommentsMemberSectionEventArgs e)
		{
			EventHandler<CancelCommentsMemberSectionEventArgs> handler = null;

			lock (DeleteSectionEventLock)
			{
				handler = DeleteSectionEvent;
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="Save"/> method creates a file for a document that doesn't exist.
		/// </summary>
		public event EventHandler<FileEventArgs> FileCreated
		{
			add
			{
				lock (FileCreatedEventLock)
				{
					FileCreatedEvent += value;
				}
			}
			remove
			{
				lock (FileCreatedEventLock)
				{
					FileCreatedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="FileCreated" /> event.
		/// </summary>
		/// <param name="e"><see cref="FileEventArgs" /> object that provides the arguments for the event.</param>
		private void OnFileCreated(FileEventArgs e)
		{
			EventHandler<FileEventArgs> handler = null;

			lock (FileCreatedEventLock)
			{
				handler = FileCreatedEvent;
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region ISupportInitialize Members
		public void BeginInit()
		{
			initializing = true;

			foreach (CommentsDocument document in assemblyDocuments.Values)
				document.BeginInit();
		}

		public void EndInit()
		{
			initializing = false;

			foreach (CommentsDocument document in assemblyDocuments.Values)
				document.EndInit();
		}
		#endregion
	}

	/// <summary>
	/// Provides arguments for the <see cref="CommentsManager.FileChanged"/> event.
	/// </summary>
	[Serializable]
	public class FileEventArgs : EventArgs
	{
		#region Public Properties
		public string File
		{
			get
			{
				return file;
			}
		}
		#endregion

		#region Private / Protected
		private readonly string file;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="FileEventArgs" /> class.
		/// </summary>
		public FileEventArgs(string file)
		{
			if (string.IsNullOrEmpty(file))
				throw new ArgumentException(Errors.NullOrEmpty, "file");

			this.file = file;
		}
		#endregion
	}
}
